package com.cman777.springc.ratelimiter;


import com.cman777.springc.ratelimiter.alg.Alg;
import com.cman777.springc.ratelimiter.alg.AlgFactory;
import com.cman777.springc.ratelimiter.collector.ServletCollector;
import com.cman777.springc.ratelimiter.collector.ServletCollectorRegistry;
import com.cman777.springc.ratelimiter.collector.target.Target;
import com.cman777.springc.ratelimiter.collector.target.Target2ValHandler;
import com.cman777.springc.ratelimiter.rule.Dimension;
import com.cman777.springc.ratelimiter.rule.RateRule;
import com.cman777.springc.ratelimiter.rule.RateRuleImporter;
import com.cman777.springc.ratelimiter.rule.RateRuleSelector;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author chenzhicong
 * @time 2020/9/17 17:03
 */
@Slf4j
public class RateLimiterClient {
    /**
     * 规则查询器
     */
    private RateRuleSelector rateRuleSelector1;
    private RateRuleSelector rateRuleSelector2;
    private RateRuleImporter rateRuleImporter;
    /**
     * servlet数据收集器注册中心
     */
    private ServletCollectorRegistry servletCollectorRegistry;
    /**
     * 算法工厂
     */
    private AlgFactory algFactory;

    /**
     * 每条规则都有一个计数器
     */
    private Map<RateRule, Map<String, Alg>> algsMap = new ConcurrentHashMap<>();

    private volatile boolean flag = true;

    public RateLimiterClient(RateRuleImporter rateRuleImporter, ServletCollectorRegistry servletCollectorRegistry, AlgFactory algFactory) {
        this.rateRuleSelector1 = new RateRuleSelector(rateRuleImporter);
        this.rateRuleSelector2 = rateRuleSelector1;
        this.servletCollectorRegistry = servletCollectorRegistry;
        this.algFactory = algFactory;
        this.rateRuleImporter = rateRuleImporter;

    }

    public RateRuleSelector getRateRuleSelector() {
        if (flag) {
            return rateRuleSelector1;
        } else {
            return rateRuleSelector2;
        }
    }

    /**
     * 重载配置的规则
     */
    public void reloadRules() {
        if (flag) {
            this.rateRuleSelector2 = new RateRuleSelector(rateRuleImporter);
            flag = false;
        } else {
            this.rateRuleSelector1 = new RateRuleSelector(rateRuleImporter);
            flag = true;
        }
    }

    /**
     * 处理servlet限流
     */
    public boolean handle4Servlet(HttpServletRequest request) {
        boolean needLimit = false;
        //获取当前rateRuleSelector的快照，因为热更新，并且需要保持一致性
        RateRuleSelector rateRuleSelector = this.getRateRuleSelector();
        //获取当前维度进行遍历
        for (Dimension dimension : rateRuleSelector.getDimensions()) {
            //获取维度下相应的收集器，并转换为value
            ServletCollector collector = servletCollectorRegistry.getServletCollector(dimension.getCollector());
            if (collector == null) {
                continue;
            }
            Target target = collector.collectTarget4Servlet(request);
            List<String> attributes = Arrays.stream(dimension.getAttributes().split(",")).collect(Collectors.toList());
            String value = Target2ValHandler.toValue(target, attributes);
            //根据维度找到对应的规则
            RateRule rateRule = rateRuleSelector.selectRule4Val(dimension, value);
            if (rateRule == null) {
                continue;
            }
            //然后根据value和维度找到规则，通过规则和值就可以找到对应的算法（每一个维度下的个体都对应一个算法）
            Alg alg = getAlgByRateRule(rateRule, value);
            //通过算法就可以判断该维度是否需要限制
            if (alg != null) {
                log.info("对请求进行限流校验，target={},rateRule={}", target, rateRule);
            }
            if (alg != null && !alg.tryAcquire()) {
                needLimit = true;
                break;
            }
        }
        if (needLimit) {
            log.info("该请求已限流");
        }
        return needLimit;
    }


    /**
     * 根据规则和val获取算法对象
     */
    private Alg getAlgByRateRule(RateRule rateRule, String val) {
        Map<String,Alg> algsMap4Val = new ConcurrentHashMap<>();
        Map<String,Alg> algsMap4ValOld = algsMap.putIfAbsent(rateRule, algsMap4Val);
        if(algsMap4ValOld != null){
            algsMap4Val = algsMap4ValOld;
        }
        Alg alg = algFactory.createAlg(rateRule, val);
        Alg algOld = algsMap4Val.putIfAbsent(val,alg);
        if(algOld != null){
            alg = algOld;
        }
        return alg;
    }


}
